/*
 * Javascript klib (js-klib) v 0.1
 * Collection of usefull Javascript routines and decorators.
 * http://code.google.com/p/js-klib/
 * 
 * Copyright 2010, Tomasz Maćkowiak
 * kurazu on kurazu.net
 *
 * Licensed under the MIT license
 */
(function(window) {
	var privates, klib;
	privates = {
		join: [].join,
		namedWithInner: function(_function, original, prefix) {
			var name = klib.functionName(original);
			if (prefix) {
				name = prefix + name;
			}
			return klib.augmented(_function, {name: name, inner: original});
		}
	};
	klib = {
		version: '0.1+trunk',
		// Attempts to log arguments to console if one is present.
		log: function() {
			try {
				if (console) {
					console.log.apply(console, arguments);
				}
			} catch (e) {
				// ignore
			}
		},
		// Returns name of function or "anonymous" is name is empty.
		functionName: function(_function) {
			return _function.name ? _function.name : "anonymous";
		},
		// Adds properties to an object. Returns augmented object.
		augmented: function(_function, dict) {
			var key;
			for (key in dict) {
				if (dict.hasOwnProperty(key)) { 
					_function[key] = dict[key];
				}
			}
			return _function;
		},
		// Returns wrapped function that logs every successfull function
		// execution to console.
		logged: function(_function) {
			return privates.namedWithInner(function() {
				var result = _function.apply(this, arguments);
				klib.log(klib.functionName(_function) + "(", arguments, ')=',
						 result);
				return result;
			}, _function, 'logged_');
		},
		// Returns wrapped function that ignores any exceptions thrown
		// by original function. Optional second parameter defines value that
		// should be returned from function, when exception is thrown. Optional
		// true third argument forces exceptions to be logged to console.
		suppressed: function(_function, defaultResult, logExceptions) {
			return privates.namedWithInner(function() {
				try {
					return _function.apply(this, arguments);
				} catch (e) {
					if (logExceptions) {
						klib.log(e);
					}
					return defaultResult;
				}
			}, _function, 'suppressed_');
		},
		// Returns wrapped function that caches the results of original
		// function execution. Arguments to function should have sane string
		// representation.
		cached: function(_function) {
			var cache = {};
			return klib.augmented(privates.namedWithInner(function() {
				var key = privates.join.call(arguments, '\u0000'),
				    fromCache = cache[key],
				    result;
				if (fromCache === undefined) {
					result = _function.apply(this, arguments);
					cache[key] = result;
					return result;
				} else {
					return fromCache;
				}
			}, _function, 'cached_'), {cache: cache});
		},
		// Wraps function to provide default values for some parameters.
		// Arguments after _function should be default values for arguments of
		// original function. Default value will be used only if runtime
		// parameter value if undefined.
		defaultValues: function(_function) {
			var defaults = arguments;
			return privates.namedWithInner(function() {
				var args = [], i, value;
				for (i = 0; i < defaults.length - 1 || i < arguments.length; i++) {
					value = arguments[i];
					if (value === undefined) {
						value = defaults[i + 1];
					}
					args.push(value);
				}
				return _function.apply(this, args);
			}, _function, 'defaultValues_');
		},
		// Returns wrapped function that measures the original function
		// execution time. The time is logged to console or passed to optional
		// callback function as only argument.
		timed: function(_function, callback) {
			return privates.namedWithInner(function() {
				var start = (new Date()).getTime(), result, diff;
				result = _function.apply(this, arguments);
				diff = (new Date()).getTime() - start;
				if (callback) {
					callback(diff);
				} else {
					klib.log(klib.functionName(_function), diff);
				}
				return result;
			}, _function, 'timed_');
		},
		// Returns wrapped function that has some arguments bound to given
		// values. Arguments passed to this function after original function
		// will be added to the beginning of parameter list for each call to
		// wrapped function.
		bound: function(_function) {
			var outer_arguments = arguments;
			return privates.namedWithInner(function() {
				var args = [], i;
				for (i = 1; i < outer_arguments.length; i++) {
					args.push(outer_arguments[i]);
				}
				for (i = 0; i < arguments.length; i++) {
					args.push(arguments[i]);
				}
				return _function.apply(this, args);
			}, _function, 'bound_');
		},
		// Returns wrapped function that ignores return value of original function
		// but always returns value passed as second argument to decorator.
		returning: function(_function, value) {
			return privates.namedWithInner(function() {
				_function.apply(this, arguments);
				return value;
			}, _function, 'returning_');
		},
		// Returns wrapped function that accepts single parameter - JavaScript
		// object, that will be used to retrieve named parameters. Arguments
		// to decorator should be ordered strings - names of parameters 
		keywordParams: function(_function) {
			var names = arguments;
			return privates.namedWithInner(function(dict) {
				var args = [], i;
				for (i = 1; i < names.length; i++) {
					args.push(dict[names[i]]);
				}
				return _function.apply(this, args);
			}, _function, 'keywordParams_');
		},
		// Wraps a function that takes multiple arguments into a function
		// that takes single argument - array of arguments.
		tupled: function(_function) {
			return privates.namedWithInner(function(args) {
				return _function.apply(this, args);
			}, _function, 'tupled_');
		},
		// Wraps a function that takes single argument - array into a function
		// that takes multiple arguments.
		untupled: function(_function) {
			return privates.namedWithInner(function() {
				return _function.call(this, arguments);
			}, _function, 'untupled_');
		},
		// Returns wrapped function that will call original function with
		// parameters transformed by defined callback functions
		paramsTransformed: function(_function) {
			var callbacks = arguments;
			return privates.namedWithInner(function() {
				var args = [], i, callback, value;
				for (i = 0; i < arguments.length; i++) {
					callback = callbacks[i + 1];
					value = callback === undefined ? arguments[i] : callback(arguments[i]);
					args.push(value);
				}
				return _function.apply(this, args);
			}, _function, 'paramsTransformed_');
		},
		// Returns wrapped function, that will call original function with
		// parameters in inverted order.
		paramsInverted: function(_function) {
			return privates.namedWithInner(function() {
				var args = [], i;
				for (i = arguments.length - 1; i >= 0; i--) {
					args.push(arguments[i]);
				}
				return _function.apply(this, args);
			}, _function, 'paramsInverted_');
		},
		// Returns wrapped function that will be executed with timer.
		// Timeout is optional execution timeout in miliseconds, default is "1". 
		// "callback" is optional callback function that will be executed once
		// the original function finishes with single argument - original
		// function result
		threaded: function(_function, timeout, callback) {
			return privates.namedWithInner(function() {
				var args = arguments,
				    self = this;
				if (timeout === undefined) {
					timeout = 1;
				}
				setTimeout(function() {
					var result = _function.apply(self, args);
					if (callback) {
						callback(result);
					}
				}, timeout);
			}, _function, 'threaded_');
		},
		// Wraps elements of "object" with given "decorators". "decorators"
		// should be an object ("{}") with property name and decorator function.
		// Returns modified "object".
		patch: function(object, decorators) {
			var key;
			for (key in decorators) {
				if (decorators.hasOwnProperty(key)) {
					object[key] = decorators[key](object[key]);
				}
			}
			return object;
		}
	};
	window.klib = klib;
})(window);
